package edu.towson.cosc.cosc455.mdapice.compiler;

import java.util.ArrayList;

import edu.towson.cosc.cosc455.interfaces.LexicalAnalyzer;

/**
 * COSC 455 Programming Languages: Implementation and Design.
 * 
 * A Simple Lexical Analyzer interfaced by Josh Dehlinger (2013) and
 * modified by Michelle D'Apice (2014).
 * 
 * Reads in in one character at a time from the input file.
 * Lexical Analyzer has two goals:
 * 1. Get tokens: through getNextToken() and getCharacter() methods
 * 2. Report any lexical errors (if any found, exit program)
 * If no errors found, sends tokens to Syntax Analyzer.
 * 
 * @author Michelle D'Apice
 */

public class MyLexicalAnalyzer implements LexicalAnalyzer {
	
	/** The next character. */
    private char nextCharacter = ' ';

    /** The current position. */
    private int currentPosition = 0;
    
    /** The current character */
    private char c = ' ';
    
    /** To hold any plaintext found in input file*/
    private String text = "";
    
    /**
	 * This is method gets the next character from the input and places it in
	 * the token class variable from the Compiler.
	 *
	 * @return the token
	 */
	@Override
	public void getNextToken() {
		
		//This is the public method to be called when the Syntax Analyzer needs a new token to be parsed.
		/* There are 4 possible cases:
		 * 1) space 
		 * 2) # 
		 * 3) legal text char 
		 * 4) error
		 */
		
		//get next character from input string, then choose path
		c = getCharacter();
		
		if (isSpace(c)){ //continues reading from string until not a space
			while(isSpace(c)){
				c = getCharacter();
			}
		}
		if(c == '#'){ 
			MyCompiler.token = processTag(); //method checks whether tag is legal
			if(!lookupToken()){
				System.err.println("LEXICAL ERROR: Invalid tag1. " + MyCompiler.token + " is an invalid tag.");
				System.exit(0);
			}
		}
		else if((!isSpace(c)) && !(c == '#')){  //if not a space or tag, it is a character
			text = processText(); //method reads from string until another tag is reached
			//return/set text
			MyCompiler.token = text;
			currentPosition--;
		}
		else{
			System.err.println("LEXICAL ERROR: " + MyCompiler.token + " is an invalid token.");
			System.exit(0);
		}	
	}//end of getNextToken method
	
	/**
	 * This method 
	 * 
	 * @return string of plaintext
	 */
	public String processText(){
		text = "";
		while(!(c == '#') && (currentPosition < MyCompiler.fileContent.length())){
			text += c;
			c = getCharacter();
		}
		return text;
	}//end of processText method
	
	/**
	 * This method
	 * 
	 * @return the entire tag, as a string
	 */
	public String processTag(){
		/*
		 * There are three types of tags that could be encountered:
		 * #text_
		 * #text_ text_
		 * #text_ text_ text_
		 */
		
		String tag = "";//string variable so method returns entire tag to be processed by lookup
		//call helper method to retrieve each word in the tag
		tag = tagHelper(tag);
		
		//check what type of tag is to determine what case to go to next
		if(!lookupTag(tag)){
			System.err.println("LEXICAL ERROR: Invalid tag2. " + tag + " is an invalid tag.");
			System.exit(0);
		}
		else{
			//one word tag - just return
			if(LexicalTokens.ONEWTAGS.contains(tag.toUpperCase())){
				return tag;
			}
			//two word tag - check first word, then second word
			else if(LexicalTokens.TWOWTAGS.contains(tag.toUpperCase())){
				String pt2 = tag; //saving tag in its current state
				tag += " "; //add space between words
				c = MyCompiler.fileContent.charAt(currentPosition++);
				tag = tagHelper(tag);
				if(lookupTag(tag.substring(pt2.length()+1))){ //send only second part to lookup method
					return tag;
				}
				else{
					System.err.println("LEXICAL ERROR: Invalid two word tag: " + tag);
					System.exit(0);
				}
			}
			//three word tag - check first, then second, then third word
			else if(LexicalTokens.THREEWTAGS.contains(tag.toUpperCase())){
				String pt2 = tag; //saving tag in its current state
				tag += " "; //add space between words
				c = MyCompiler.fileContent.charAt(currentPosition++);
				tag = tagHelper(tag);
				if(lookupTag(tag.substring(pt2.length()+1))){ //send only second part to lookup method
					String pt3 = tag; //saving tag in its current state
					tag += " "; //add space between words
					c = MyCompiler.fileContent.charAt(currentPosition++);
					tag = tagHelper(tag);
					if(lookupTag(tag.substring(pt3.length()+1))){ //send only last part to lookup method
						return tag;
					}
					else{
						System.err.println("LEXICAL ERROR: Invalid three word tag: " + tag);
						System.exit(0);
					}
				}
				else{
					System.err.println("LEXICAL ERROR: Invalid three word tag: " + tag);
					System.exit(0);
				}
			}
		}//end of outermost else
				
		return tag;
	}//end of processTag method
	
	/**
	 * This method simply runs through the tag being processed until a space is found.
	 * 
	 * @param tag
	 * @return portion of tag as a string
	 */
	public String tagHelper(String tag){
		while(!isSpace(c)){
			tag += c;
			c = getCharacter();
		}
		
		return tag;
	}//end of tagHelper method
	
	/**
	 * This method takes the portions of tags being processed and checks whether they are valid in the language.
	 * 
	 * @param tag
	 * @return true if portion of tag is valid, false if it is not
	 */
	public boolean lookupTag(String tag){
		//checks each and every word that could be found within a tag
		ArrayList<String> tags = new ArrayList<String>();
		tags.add("#HAI"); 	tags.add("#OBTW");	tags.add("#VISIBLE"); tags.add("#MAEK"); tags.add("#GIMMEH"); tags.add("#I"); tags.add("HEAD"); tags.add("TITLE");
		tags.add("PARAGRAF"); tags.add("BOLD"); tags.add("ITALICS"); tags.add("LIST"); tags.add("ITEM"); tags.add("NEWLINE"); tags.add("SOUNDZ"); tags.add("VIDZ");
		tags.add("HAS");	tags.add("A"); tags.add("#TLDR");	tags.add("#KTHXBYE");	tags.add("#OIC");	tags.add("#MKAY"); tags.add("#ITZ");
		
		
		if(tags.contains(tag.toUpperCase())){
			return true;
		}
		else{
			return false;
		}
	}//end of lookupTag method

	/**
	 * This method gets the next character from the input and places it in
	 * the nextCharacter class variable.
	 *
	 * @return the character
	 */
	@Override
	public char getCharacter() {
		
		if(currentPosition < MyCompiler.fileContent.length()){
			nextCharacter = MyCompiler.fileContent.charAt(currentPosition++);
		}
		else{
			nextCharacter = '\n';
		}
		
		return nextCharacter;

	}//end of getCharacter method

	/**
	 * This method gets the next character from the input and places it in
	 * the nextCharacter class variable.
	 *
	 * @param c the current character
	 * @return true, if is space; otherwise false
	 */
	@Override
	public boolean isSpace(char c) {
		if (c == ' ') return true;
		else return false;
	}//end isSpace method

	/**
	 * This method checks to see if the current, possible token is legal in the
	 * defined grammar.
	 *
	 * @return true, if it is a legal token, otherwise false
	 */
	@Override
	public boolean lookupToken() {
		if((MyCompiler.token.equalsIgnoreCase(LexicalTokens.DOC_BEGIN)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.DOC_END)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.HEAD_BEGIN)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.COMMENT_BEGIN)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.TITLE_BEGIN)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.PARA_BEGIN)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.BOLD_BEGIN)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITAL_BEGIN)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.LIST_BEGIN)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITEM_BEGIN)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.NEWLINE)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.SOUND_BEGIN)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.VID_BEGIN)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.VAR_BEGIN)) 
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.VISIBLE)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.OIC))
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.MKAY)) || (MyCompiler.token.equalsIgnoreCase(LexicalTokens.TLDR))
				|| (MyCompiler.token.equalsIgnoreCase(LexicalTokens.ITZ))){
			return true;
		}
		else{
			return false;
		}
	}//end of lookupToken method

}//end of public class MyLexicalAnalyzer